use std::ops::Add;

use dodgy_2d::Vec2;
use rstar::{Point, PointDistance, RTreeObject, AABB};
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "napi")]
use napi_derive::napi;

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "napi", napi(object))]
pub struct F64Point {
    pub x: f64,
    pub y: f64,
}

impl Add for F64Point {
    type Output = F64Point;

    fn add(self, other: Self) -> Self::Output {
        F64Point::new(self.x + other.x, self.y + other.y)
    }
}

impl Add for &F64Point {
    type Output = F64Point;

    fn add(self, other: Self) -> Self::Output {
        F64Point::new(self.x + other.x, self.y + other.y)
    }
}

impl F64Point {
    pub fn length(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }

    pub fn normalized(&self) -> F64Point {
        let len = self.length();
        if len > 0.0 {
            F64Point::new(self.x / len, self.y / len)
        } else {
            self.clone()
        }
    }

    pub fn angle_between(&self, other: &F64Point) -> f64 {
        let dot = self.x * other.x + self.y * other.y;
        let det = self.x * other.y - self.y * other.x;
        det.atan2(dot)
    }

    pub fn distance(&self, other: &F64Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

impl PartialEq for F64Point {
    fn eq(&self, other: &Self) -> bool {
        f64_eq(self.x, other.x) && f64_eq(self.y, other.y)
    }
}

fn f64_eq(a:f64, b:f64)->bool{
    (a - b).abs() < f64::EPSILON
}

impl Point for F64Point {
    type Scalar = f64;

    const DIMENSIONS: usize = 2;

    fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self {
        let x = generator(0);
        let y = generator(1);
        Self::new(x, y)
    }

    fn nth(&self, index: usize) -> Self::Scalar {
        match index {
            0=>self.x,
            1=>self.y,
            _=>0f64
        }
    }

    fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar {
        match index {
            0=>&mut self.x,
            1=>&mut self.y,
            _=>&mut self.x
        }
    }
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[cfg_attr(feature = "napi", napi)]
impl F64Point {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }
}

impl From<(f64, f64)> for F64Point {
    fn from(value: (f64, f64)) -> Self {
        Self { x: value.0, y: value.1 }
    }
}

impl From<&(f64, f64)> for F64Point {
    fn from(value: &(f64, f64)) -> Self {
        Self { x: value.0, y: value.1 }
    }
}

impl Into<Vec2> for F64Point {
    fn into(self) -> Vec2 {
        Vec2::new(self.x as f32, self.y as f32)
    }
}

impl Into<Vec2> for &F64Point {
    fn into(self) -> Vec2 {
        Vec2::new(self.x as f32, self.y as f32)
    }
}

type F64PointTuple = (f64, f64);

impl From<F64Point> for F64PointTuple{
    fn from(value: F64Point) -> Self {
        (value.x, value.y)
    }
}

impl From<&F64Point> for F64PointTuple{
    fn from(value: &F64Point) -> Self {
        (value.x, value.y)
    }
}

pub(crate) type Edge = (F64Point, F64Point);


#[derive(Serialize, Clone, Debug, Deserialize)]
pub(crate) struct NavMeshData {
    pub points: Vec<F64Point>,
    pub polygons: Vec<NavPolygonData>,
    pub neighbors: Vec<Vec<usize>>, 
    pub merged_polygon_indexs: Vec<Vec<Vec<usize>>>
}

#[cfg_attr(feature = "napi", napi(object))]
pub struct NapiNavMeshData {
    pub points: Vec<F64Point>,
    pub polygons: Vec<NapiNavPolygonData>,
    pub neighbors: Vec<Vec<f64>>, 
    pub merged_polygon_indexs: Vec<Vec<Vec<f64>>>
}

impl Into<NavMeshData> for NapiNavMeshData {
    fn into(self) -> NavMeshData {
        NavMeshData {
            points: self.points,
            polygons: self.polygons.into_iter().map(|x| x.into()).collect(),
            neighbors: self.neighbors.iter().map(|x| x.iter().map(|x| *x as usize).collect()).collect(),
            merged_polygon_indexs: self.merged_polygon_indexs.iter().map(|x| x.iter().map(|x| x.iter().map(|x| *x as usize).collect()).collect()).collect()
        }
    }
}

impl From<NavMeshData> for NapiNavMeshData {
    fn from(value: NavMeshData) -> Self {
        Self {
            points: value.points,
            polygons: value.polygons.iter().map(|x| x.clone().into()).collect(),
            neighbors: value.neighbors.iter().map(|x| x.iter().map(|x| *x as f64).collect()).collect(),
            merged_polygon_indexs: value.merged_polygon_indexs.iter().map(|x| x.iter().map(|x| x.iter().map(|x| *x as f64).collect()).collect()).collect()
        }
    }
}

#[derive(Serialize, Clone, Debug, Deserialize)]
pub(crate) struct NavPolygonData {
    pub point_indices: Vec<usize>,
    pub centroid: F64Point,
    pub polygon_index: usize,
}

#[cfg_attr(feature = "napi", napi(object))]
pub struct NapiNavPolygonData {
    pub point_indices: Vec<f64>,
    pub centroid: F64Point,
    pub polygon_index: f64,
}

impl Into<NavPolygonData> for NapiNavPolygonData {
    fn into(self) -> NavPolygonData {
        NavPolygonData {
            point_indices: self.point_indices.iter().map(|x| *x as usize).collect(),
            centroid: self.centroid,
            polygon_index: self.polygon_index as usize,
        }
    }
}

impl From<NavPolygonData> for NapiNavPolygonData {
    fn from(value: NavPolygonData) -> Self {
        Self {
            point_indices: value.point_indices.iter().map(|x| *x as f64).collect(),
            centroid: value.centroid,
            polygon_index: value.polygon_index as f64,
        }
    }
}

impl NavMeshData {
    pub fn from_json(json: &str) -> Option<Self> {
        let result = serde_json::from_str::<Self>(json);
        match result {
            Ok(data) => Some(data),
            Err(e) => {
                eprintln!("parse json error: {}", e);
                None
            }
        }
    }

    ///
    /// 生成行走区域的边的信息
    /// 线段方向顺时针表示无法从右边走到左边
    /// 线段方向逆时针表示无法从左边走到右边
    /// 
    pub fn get_walkable_polygon_edges(&self)->Vec<Edge> {
        let mut result: Vec<_> = Vec::new();
        //遍历merged_polygon_indexs
        for merged_polygon_indexs in &self.merged_polygon_indexs {
            for points_indexs in merged_polygon_indexs {
                let polygon_indexs_len = points_indexs.len();
                for index in 0..polygon_indexs_len {
                    let start = points_indexs[index];
                    let end = points_indexs[(index + 1) % polygon_indexs_len];
                    let start_point = self.points[start];
                    let end_point = self.points[end];
                    result.push((start_point, end_point));
                }
            }
        }
        result
    }

    pub(crate) fn gen_aabb_objects(&self) -> Vec<AABBObject> {
        let mut aabb_objects = Vec::new();
        for (i, polygon) in self.polygons.iter().enumerate() {
            let mut min_x = f64::MAX;
            let mut min_y = f64::MAX;
            let mut max_x = f64::MIN;
            let mut max_y = f64::MIN;
            for &point_index in &polygon.point_indices {
                let point = self.points[point_index];
                if point.x < min_x {
                    min_x = point.x;
                }
                if point.y < min_y {
                    min_y = point.y;
                }
                if point.x > max_x {
                    max_x = point.x;
                }
                if point.y > max_y {
                    max_y = point.y;
                }
            }
            aabb_objects.push(AABBObject {
                min: F64Point { x: min_x, y: min_y },
                max: F64Point { x: max_x, y: max_y },
                id: i,
            });
        }
        aabb_objects
    }

    fn compute_shared_edge(&self, a:&NavPolygonData, b:&NavPolygonData)->Option<Edge> {
        let mut b_edges:FxHashSet<(usize, usize)> = FxHashSet::default();
        let b_indices = &b.point_indices;
        let b_length = b_indices.len();
        for index in 0..b_length {
            let Some(point_index1) = b_indices.get(index) else { eprintln!("indice index {index} not valid"); continue;};
            let index2 = (index + 1) % b_length;
            let Some(point_index2) =  b_indices.get(index2) else {eprintln!("indice index {index2} not valid"); continue;};
            let result = if point_index1 > point_index2 {
                (*point_index2, *point_index1)
            } else {
                (*point_index1, *point_index2)
            };
            b_edges.insert(result);
        }

        let a_indices = &a.point_indices;
        let a_length = a_indices.len();
        for index in 0..a_length {
            let Some(point_index1) = a_indices.get(index) else { eprintln!("indice index {index} not valid"); continue;};
            let index2 = (index + 1) % a_length;
            let Some(point_index2) =  a_indices.get(index2) else {eprintln!("indice index {index2} not valid"); continue;};
            let result = if point_index1 > point_index2 {
                (*point_index2, *point_index1)
            } else {
                (*point_index1, *point_index2)
            };
            if b_edges.contains(&result){
                let Some(p0) = self.points.get(*point_index1) else { eprintln!("point index {point_index1} not valid"); continue;};
                let Some(p1) = self.points.get(*point_index2) else { eprintln!("point index {point_index2} not valid"); continue;};
                return Some((p0.to_owned(), p1.to_owned()));
            }
        }
        None
    }

    fn get_neighbors(&self, index:usize)->Option<&[usize]>{
        let Some(neighors) = self.neighbors.get(index) else {return None};
        return Some(&neighors);
    }

    pub(crate) fn gen_portals(&self)->FxHashMap<(usize, usize), Edge>{
        let mut result:FxHashMap<(usize, usize), Edge> = FxHashMap::default();
        let polygons_len = self.polygons.len();
        for index in 0 .. polygons_len {
            let Some(polygon) = self.polygons.get(index) else { eprintln!("polygon index {index} not valid"); continue;};
            let Some(neighbor_indices) = self.get_neighbors(index) else { eprintln!("neighbor_indices index {index} not valid"); continue;};
            for index2 in 0..polygons_len {
                if index2 == index {//不检查自己
                    continue;
                }
                let Some(neighbor) = self.polygons.get(index2) else { eprintln!("neighbor index {index2} not valid"); continue;};
                if neighbor_indices.contains(&index2){
                    if let Some(edge) = self.compute_shared_edge(polygon, neighbor){
                        result.insert((index, index2), edge);
                    }
                }
            }
        }
        result
    }

    //能够保证是凸多边形，所以使用向量叉乘来判断点是否在多边形内部
    pub fn polygon_contains_point(&self, polygon_index:usize, point:&F64Point)->bool{
        let Some(polygon) = self.polygons.get(polygon_index) else { eprintln!("polygon index {polygon_index} not valid"); return false;};
        let vertices = &polygon.point_indices;
        let length = vertices.len();
        let mut sign:i8 = 0;//0表示位置,1表示在左边,-1表示在右边
        for index in 0..length {
            let Some(p1) = self.points.get(vertices[index]) else { eprintln!("point index {} not valid", vertices[index]); return false;};
            let Some(p2) = self.points.get(vertices[(index + 1) % length]) else { eprintln!("point index {} not valid", vertices[(index + 1) % length]); return false;};
            let v1 = F64Point {x: p2.x - p1.x, y: p2.y - p1.y};
            let v2 = F64Point {x: point.x - p1.x, y: point.y - p1.y};
            let cross = v1.x * v2.y - v1.y * v2.x;
            if cross == 0.0 {
                continue;
            }
            if sign == 0 {
                sign = if cross > 0.0 {1} else {-1};
            } else {
                if sign == 1 && cross < 0.0 {
                    return false;
                }
                if sign == -1 && cross > 0.0 {
                    return false;
                }
            }
        }

        true

    }

    // pub fn polygon_sqrdistance(&self, start:usize, end:usize)->f64 {
    //     let Some(start_polygon) = self.polygons.get(start) else { eprintln!("polygon index {} not valid", start); return f64::INFINITY;};
    //     let Some(end_polygon) = self.polygons.get(end) else { eprintln!("polygon index {} not valid", end); return f64::INFINITY;};
    //     let dx = start_polygon.centroid.x - end_polygon.centroid.x;
    //     let dy = start_polygon.centroid.y - end_polygon.centroid.y;
    //     dx * dx + dy * dy
    // }

    // 曼哈顿距离的启发函数
    pub fn polygon_manhattan_dist(&self, start:usize, end:usize)->f64 {
        let Some(start_polygon) = self.polygons.get(start) else { eprintln!("polygon index {} not valid", start); return f64::INFINITY;};
        let Some(end_polygon) = self.polygons.get(end) else { eprintln!("polygon index {} not valid", end); return f64::INFINITY;};
        let dx = start_polygon.centroid.x - end_polygon.centroid.x;
        let dy = start_polygon.centroid.y - end_polygon.centroid.y;
        dx.abs() + dy.abs()
    }

    pub fn get_polygon_center_pos(&self, index:usize)->Option<F64Point> {
        let Some(start_polygon) = self.polygons.get(index) else { eprintln!("polygon index {} not valid", index); return None;};
        Some(start_polygon.centroid.clone())
    }
}

#[derive(Debug)]
pub struct AABBObject {
    pub min: F64Point,
    pub max: F64Point,
    pub id: usize,
}


impl RTreeObject for AABBObject {
    type Envelope = AABB<F64Point>;

    fn envelope(&self) -> Self::Envelope {
        AABB::from_corners(self.min, self.max)
    }
}

impl PointDistance for AABBObject {
    fn distance_2(
        &self,
        point: &<Self::Envelope as rstar::Envelope>::Point,
    ) -> <<Self::Envelope as rstar::Envelope>::Point as Point>::Scalar {
        let dx = if point.x < self.min.x {
            self.min.x - point.x
        } else if point.x > self.max.x {
            point.x - self.max.x
        } else {
            0.0
        };

        let dy = if point.y < self.min.y {
            self.min.y - point.y
        } else if point.y > self.max.y {
            point.y - self.max.y
        } else {
            0.0
        };

        dx * dx + dy * dy
    }
}
